﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace __ts.Core.Utilities
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TValue"></typeparam>
    /// <typeparam name="KeyType"></typeparam>
    public class Cacher<TValue, KeyType> where TValue : IItem<KeyType>
    {
        #region Fields

        private static object _syncRoot = new object();
        private int _itemTotal = 0;
        private int _maxItems = 0;
        private int _ratio = 0;

        #endregion

        #region Constructors

        protected Cacher(int maxItems, int removeRatio)
        {
            _maxItems = maxItems;
            _ratio = removeRatio;

            Dict = new Dictionary<KeyType, WeakReference>(10000 * 50);
            Nodes = new LinkedList<TValue>();
        }

        protected Cacher(int maxItems)
            : this(maxItems, 2)
        {
        }

        #endregion

        #region Methods

        protected void Add(TValue value)
        {
            try
            {
                if (Dict.ContainsKey(value.Key)) return;

                if (_itemTotal > _maxItems) Remove();
                lock (_syncRoot) Nodes.AddFirst(value);

                var weak = new WeakReference(value, false);
                Dict[value.Key] = weak;
            }
            finally
            {
                Interlocked.Increment(ref _itemTotal);
            }
        }

        protected TValue Get(KeyType key)
        {
            var value = default(TValue);
            WeakReference weak;
            if (!Dict.TryGetValue(key, out weak) || weak.Target == null)
                return default(TValue);

            value = (TValue)weak.Target;
            lock (_syncRoot)
            {
                Nodes.Remove(value);
                Nodes.AddFirst(value);
            }
            return value;
        }

        protected void Remove()
        {
            lock (_syncRoot)
            {
                var l = (_ratio / 100) * _maxItems; var t = 0;
                do
                {
                    if (Nodes.Count == 0) return;
                    Nodes.RemoveLast();
                    Interlocked.Decrement(ref _itemTotal);
                }
                while (t++ < l);
            }
        }

        #endregion

        #region Attributes

        private Dictionary<KeyType, WeakReference> Dict
        { get; set; }

        private LinkedList<TValue> Nodes
        { get; set; }

        #endregion

    }

    public interface IItem<T>
    {
        T Key { get; set; }
    }
}
